Hrvatski

Istražite napredne uzorke React Context Providera za učinkovito upravljanje stanjem, optimizaciju performansi i sprječavanje nepotrebnih ponovnih renderiranja u vašim aplikacijama.

Uzorci React Context Providera: Optimizacija performansi i izbjegavanje problema s ponovnim renderiranjem

React Context API je moćan alat za upravljanje globalnim stanjem u vašim aplikacijama. Omogućuje vam dijeljenje podataka između komponenti bez potrebe za ručnim prosljeđivanjem propsa na svakoj razini. Međutim, neispravno korištenje Contexta može dovesti do problema s performansama, osobito do nepotrebnih ponovnih renderiranja. Ovaj članak istražuje različite uzorke Context Providera koji vam pomažu optimizirati performanse i izbjeći te zamke.

Razumijevanje problema: Nepotrebna ponovna renderiranja

Prema zadanim postavkama, kada se vrijednost Contexta promijeni, sve komponente koje koriste taj Context ponovno će se renderirati, čak i ako ne ovise o određenom dijelu Contexta koji se promijenio. To može biti značajan problem za performanse, posebno u velikim i složenim aplikacijama. Razmotrite scenarij u kojem imate Context koji sadrži korisničke informacije, postavke teme i preferencije aplikacije. Ako se promijeni samo postavka teme, idealno bi bilo da se ponovno renderiraju samo komponente povezane s temom, a ne cijela aplikacija.

Da bismo to ilustrirali, zamislite globalnu e-commerce aplikaciju dostupnu u više zemalja. Ako se promijeni preferirana valuta (što se rješava unutar Contexta), ne biste željeli da se cijeli katalog proizvoda ponovno renderira – samo prikazi cijena trebaju se ažurirati.

Uzorak 1: Memoizacija vrijednosti s useMemo

Najjednostavniji pristup sprječavanju nepotrebnih ponovnih renderiranja je memoizacija vrijednosti Contexta pomoću useMemo. To osigurava da se vrijednost Contexta mijenja samo kada se promijene njezine ovisnosti.

Primjer:

Recimo da imamo `UserContext` koji pruža korisničke podatke i funkciju za ažuriranje korisničkog profila.


import React, { createContext, useState, useMemo } from 'react';

const UserContext = createContext(null);

function UserProvider({ children }) {
  const [user, setUser] = useState({
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  });

  const updateUser = (newUserData) => {
    setUser(prevState => ({ ...prevState, ...newUserData }));
  };

  const contextValue = useMemo(() => ({
    user,
    updateUser,
  }), [user, setUser]);

  return (
    
      {children}
    
  );
}

export { UserContext, UserProvider };

U ovom primjeru, useMemo osigurava da se `contextValue` mijenja samo kada se promijeni stanje `user` ili funkcija `setUser`. Ako se nijedno ne promijeni, komponente koje koriste `UserContext` neće se ponovno renderirati.

Prednosti:

Nedostaci:

Uzorak 2: Razdvajanje odgovornosti s više Contexta

Granularniji pristup je podijeliti vaš Context na više manjih Contexta, od kojih je svaki odgovoran za određeni dio stanja. To smanjuje opseg ponovnih renderiranja i osigurava da se komponente ponovno renderiraju samo kada se promijene specifični podaci o kojima ovise.

Primjer:

Umjesto jednog `UserContexta`, možemo stvoriti odvojene contexte za korisničke podatke i korisničke preferencije.


import React, { createContext, useState } from 'react';

const UserDataContext = createContext(null);
const UserPreferencesContext = createContext(null);

function UserDataProvider({ children }) {
  const [user, setUser] = useState({
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  });

  const updateUser = (newUserData) => {
    setUser(prevState => ({ ...prevState, ...newUserData }));
  };

  return (
    
      {children}
    
  );
}

function UserPreferencesProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [language, setLanguage] = useState('en');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    
      {children}
    
  );
}

export { UserDataContext, UserDataProvider, UserPreferencesContext, UserPreferencesProvider };

Sada komponente koje trebaju samo korisničke podatke mogu koristiti `UserDataContext`, a komponente koje trebaju samo postavke teme mogu koristiti `UserPreferencesContext`. Promjene teme više neće uzrokovati ponovno renderiranje komponenti koje koriste `UserDataContext`, i obrnuto.

Prednosti:

Nedostaci:

Uzorak 3: Selektorske funkcije s prilagođenim hookovima

Ovaj uzorak uključuje stvaranje prilagođenih hookova koji izvlače specifične dijelove vrijednosti Contexta i ponovno se renderiraju samo kada se ti specifični dijelovi promijene. To je posebno korisno kada imate veliku vrijednost Contexta s mnogo svojstava, a komponenti je potrebno samo nekoliko njih.

Primjer:

Koristeći originalni `UserContext`, možemo stvoriti prilagođene hookove za odabir specifičnih korisničkih svojstava.


import React, { useContext } from 'react';
import { UserContext } from './UserContext'; // Pretpostavljajući da je UserContext u UserContext.js

function useUserName() {
  const { user } = useContext(UserContext);
  return user.name;
}

function useUserEmail() {
  const { user } = useContext(UserContext);
  return user.email;
}

export { useUserName, useUserEmail };

Sada komponenta može koristiti `useUserName` da bi se ponovno renderirala samo kada se promijeni korisničko ime, i `useUserEmail` da bi se ponovno renderirala samo kada se promijeni korisnička e-pošta. Promjene drugih korisničkih svojstava (npr. lokacije) neće pokrenuti ponovno renderiranje.


import React from 'react';
import { useUserName, useUserEmail } from './UserHooks';

function UserProfile() {
  const name = useUserName();
  const email = useUserEmail();

  return (
    

Name: {name}

Email: {email}

); }

Prednosti:

Nedostaci:

Uzorak 4: Memoizacija komponenti s React.memo

React.memo je komponenta višeg reda (HOC) koja memoizira funkcionalnu komponentu. Sprječava ponovno renderiranje komponente ako se njezini props nisu promijenili. Možete je kombinirati s Contextom kako biste dodatno optimizirali performanse.

Primjer:

Recimo da imamo komponentu koja prikazuje korisničko ime.


import React, { useContext } from 'react';
import { UserContext } from './UserContext';

function UserName() {
  const { user } = useContext(UserContext);
  return 

Name: {user.name}

; } export default React.memo(UserName);

Omotavanjem `UserName` s `React.memo`, ponovno će se renderirati samo ako se promijeni `user` prop (implicitno proslijeđen putem Contexta). Međutim, u ovom pojednostavljenom primjeru, samo `React.memo` neće spriječiti ponovno renderiranje jer se cijeli `user` objekt i dalje prosljeđuje kao prop. Da bi bio uistinu učinkovit, trebate ga kombinirati sa selektorskim funkcijama ili odvojenim contextima.

Učinkovitiji primjer kombinira `React.memo` sa selektorskim funkcijama:


import React from 'react';
import { useUserName } from './UserHooks';

function UserName() {
  const name = useUserName();
  return 

Name: {name}

; } function areEqual(prevProps, nextProps) { // Prilagođena funkcija za usporedbu return prevProps.name === nextProps.name; } export default React.memo(UserName, areEqual);

Ovdje je `areEqual` prilagođena funkcija za usporedbu koja provjerava je li se `name` prop promijenio. Ako nije, komponenta se neće ponovno renderirati.

Prednosti:

Nedostaci:

Uzorak 5: Kombiniranje Contexta i Reducera (useReducer)

Kombiniranje Contexta s useReducer omogućuje vam upravljanje složenom logikom stanja i optimizaciju ponovnih renderiranja. useReducer pruža predvidljiv uzorak upravljanja stanjem i omogućuje vam ažuriranje stanja na temelju akcija, smanjujući potrebu za prosljeđivanjem više setter funkcija kroz Context.

Primjer:


import React, { createContext, useReducer, useContext } from 'react';

const UserContext = createContext(null);

const initialState = {
  user: {
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  },
  theme: 'light',
  language: 'en'
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_USER':
      return { ...state, user: { ...state.user, ...action.payload } };
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'SET_LANGUAGE':
      return { ...state, language: action.payload };
    default:
      return state;
  }
};

function UserProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    
      {children}
    
  );
}

function useUserState() {
  const { state } = useContext(UserContext);
  return state.user;
}

function useUserDispatch() {
    const { dispatch } = useContext(UserContext);
    return dispatch;
}


export { UserContext, UserProvider, useUserState, useUserDispatch };

Sada komponente mogu pristupiti stanju i slati akcije koristeći prilagođene hookove. Na primjer:


import React from 'react';
import { useUserState, useUserDispatch } from './UserContext';

function UserProfile() {
  const user = useUserState();
  const dispatch = useUserDispatch();

  const handleUpdateName = (e) => {
    dispatch({ type: 'UPDATE_USER', payload: { name: e.target.value } });
  };

  return (
    

Name: {user.name}

); }

Ovaj uzorak promiče strukturiraniji pristup upravljanju stanjem i može pojednostaviti složenu logiku Contexta.

Prednosti:

Nedostaci:

Uzorak 6: Optimistična ažuriranja

Optimistična ažuriranja uključuju trenutno ažuriranje korisničkog sučelja kao da je akcija uspjela, čak i prije nego što poslužitelj to potvrdi. To može značajno poboljšati korisničko iskustvo, posebno u situacijama s visokom latencijom. Međutim, zahtijeva pažljivo rukovanje potencijalnim pogreškama.

Primjer:

Zamislite aplikaciju u kojoj korisnici mogu lajkati objave. Optimistično ažuriranje bi odmah povećalo broj lajkova kada korisnik klikne gumb za lajkanje, a zatim poništilo promjenu ako zahtjev poslužitelju ne uspije.


import React, { useContext, useState } from 'react';
import { UserContext } from './UserContext';

function LikeButton({ postId }) {
  const { dispatch } = useContext(UserContext);
  const [isLiking, setIsLiking] = useState(false);

  const handleLike = async () => {
    setIsLiking(true);
    // Optimistično ažuriraj broj lajkova
    dispatch({ type: 'INCREMENT_LIKES', payload: { postId } });

    try {
      // Simuliraj API poziv
      await new Promise(resolve => setTimeout(resolve, 500));

      // Ako je API poziv uspješan, ne radi ništa (UI je već ažuriran)
    } catch (error) {
      // Ako API poziv ne uspije, poništi optimistično ažuriranje
      dispatch({ type: 'DECREMENT_LIKES', payload: { postId } });
      alert('Failed to like post. Please try again.');
    } finally {
      setIsLiking(false);
    }
  };

  return (
    
  );
}

U ovom primjeru, akcija `INCREMENT_LIKES` se šalje odmah, a zatim se poništava ako API poziv ne uspije. To pruža responzivnije korisničko iskustvo.

Prednosti:

Nedostaci:

Odabir pravog uzorka

Najbolji uzorak Context Providera ovisi o specifičnim potrebama vaše aplikacije. Evo sažetka koji će vam pomoći pri odabiru:

Dodatni savjeti za optimizaciju performansi Contexta

Zaključak

React Context API je moćan alat, ali ključno je koristiti ga ispravno kako biste izbjegli probleme s performansama. Razumijevanjem i primjenom uzoraka Context Providera o kojima se govori u ovom članku, možete učinkovito upravljati stanjem, optimizirati performanse i graditi učinkovitije i responzivnije React aplikacije. Ne zaboravite analizirati svoje specifične potrebe i odabrati uzorak koji najbolje odgovara zahtjevima vaše aplikacije.

Uzevši u obzir globalnu perspektivu, programeri bi također trebali osigurati da rješenja za upravljanje stanjem rade besprijekorno u različitim vremenskim zonama, formatima valuta i regionalnim podatkovnim zahtjevima. Na primjer, funkcija za formatiranje datuma unutar Contexta trebala bi biti lokalizirana na temelju korisnikovih preferencija ili lokacije, osiguravajući dosljedne i točne prikaze datuma bez obzira na to odakle korisnik pristupa aplikaciji.